import datetime
import sys
import json
import re
import functools
import logging
import os
import xmlrpc.client
import urllib.request
from concurrent.futures import ThreadPoolExecutor

from absl import flags
import tornado.ioloop
from tornado.ioloop import PeriodicCallback
import tornado.web
import tornado.gen
from tornado.concurrent import run_on_executor

from coin.flow.simple_queue import SimpleQueue
import coin.flow.subscriber as flow_subscriber

from coin.base.timestamp import get_timestamp

from coin.exchange.shared.kr_rest.product import SharedProduct

from coin.exchange.kr_rest.feed.ws_feed_impl import generate_ws_feed_subscriber
from coin.exchange.kr_rest.feed.feed_impl import generate_feed_subscriber
from coin.exchange.bitfinex_v2.ws.subscriber import BitfinexV2Subscriber
from coin.exchange.bitfinex_v2.ws.handler import BitfinexV2Handler
from coin.exchange.bitfinex_v2.kr_rest.product import BitfinexProduct
from coin.exchange.binance.ws.subscriber import BinanceSubscriber
from coin.exchange.binance.ws.handler import BinanceHandler
from coin.exchange.binance.rest.public import BinanceBookQueryer
from coin.exchange.binance.kr_rest.product import BinanceProduct
from coin.exchange.bitmex.ws.subscriber import BitmexSubscriber
from coin.exchange.bitmex.ws.handler import BitmexHandler
from coin.exchange.bitmex.kr_rest.futures_product import BitmexFuturesProduct

flags.DEFINE_string('coin_group', '', '')

flags.DEFINE_string('basis_server_url', 'http://localhost:8090', '')

query_interval = 0.5
exchange_config = {
    "Coinone": {
        "feed_type": "kr_rest",
    },
    "Bithumb": {
        "feed_type": "kr_rest",
    },
    "Upbit": {
        "feed_type": "kr_rest_ws",
    },
    "Korbit": {
        "feed_type": "kr_rest",
    },
    "Bitfinex": {
        "feed_type": "kr_rest_ws"
    },
    "Binance": {
        "feed_type": "ws"
    },
    "Huobi": {
        "feed_type": "ws"
    },
    "Bitmex": {
        "feed_type": "ws"
    }
}
coin_groups = {
    "krw_group": [
        {
            "Coinone": {("BTC-KRW", ""), ("BCH-KRW", ""), ("XRP-KRW", ""), ("ETH-KRW", "")},
            "Bithumb": {("BTC-KRW", ""), ("BCH-KRW", ""), ("XRP-KRW", ""), ("ETH-KRW", "")},
            "Upbit": {("BTC-KRW", ""), ("BCH-KRW", ""), ("XRP-KRW", ""), ("ETH-KRW", "")},
            "Korbit": {("BTC-KRW", ""), ("BCH-KRW", ""), ("XRP-KRW", ""), ("ETH-KRW", "")},
        },
        {
            "Coinone": {("ETC-KRW", ""), ("LTC-KRW", ""), ("QTUM-KRW", "")},
            "Bithumb": {("ETC-KRW", ""), ("LTC-KRW", ""), ("QTUM-KRW", "")},
            "Upbit": {("ETC-KRW", ""), ("LTC-KRW", ""), ("QTUM-KRW", "")},
            "Korbit": {("ETC-KRW", "")},
        },
    ],
    "usd_group": [
        {
            "Coinone": {("BTC-KRW", "USD"), ("BCH-KRW", "USD"), ("XRP-KRW", "USD"),
                        ("ETH-KRW", "USD")},
            "Bithumb": {("BTC-KRW", "USD"), ("BCH-KRW", "USD"), ("XRP-KRW", "USD"),
                        ("ETH-KRW", "USD")},
            "Korbit": {("BTC-KRW", "USD"), ("BCH-KRW", "USD"), ("XRP-KRW", "USD"),
                       ("ETH-KRW", "USD")},
            "Upbit": {("BTC-KRW", "USD"), ("BCH-KRW", "USD"), ("XRP-KRW", "USD"),
                      ("ETH-KRW", "USD")},
            "Bitfinex": {("BTC-USD", ""), ("BCH-USD", ""), ("XRP-USD", ""), ("ETH-USD", "")},
        },
        {
            "Coinone": {("QTUM-KRW", "USD"), ("LTC-KRW", "USD"), ("ETC-KRW", "USD")},
            "Bithumb": {("QTUM-KRW", "USD"), ("LTC-KRW", "USD"), ("ETC-KRW", "USD")},
            "Korbit": {("ETC-KRW", "USD")},
            "Upbit": {
                ("QTUM-KRW", "USD"),
                ("LTC-KRW", "USD"),
                ("ETC-KRW", "USD"),
            },
            "Bitfinex": {
                ("QTUM-USD", ""),
                ("LTC-USD", ""),
                ("ETC-USD", ""),
            },
        },
    ],
    "hk_group": [
        {
            "Bitfinex": {
                ("BTC-USD", ""),
                ("ETH-USD", ""),
                ("NEO-USD", ""),
            },
            "Binance": {
                ("BTC-USD", ""),
                ("ETH-USD", ""),
                ("NEO-USD", ""),
            },
        },
        {
            "Bitfinex": {("BTC-USD", ""),},
            "Bitmex": {("BTC-USD", ""),},
        },
    ]
}
basis_pairs = {
    "krw_group": [("Coinone", "Bithumb"), ("Upbit", "Bithumb"), ("Upbit", "Coinone"),
                  ("Upbit", "Korbit"), ("Coinone", "Korbit")],
    "usd_group": [("Coinone", "Bitfinex"), ("Bithumb", "Bitfinex"), ("Korbit", "Bitfinex"),
                  ("Upbit", "Bitfinex")],
    "hk_group": [("Bitfinex", "Binance"), ("Bitfinex", "Bitmex")],
}

# If queried value is not within
# [forex_reference * (1 - forex_valid_range),
#  forex_reference * (1 + forex_valid_range)],
# the value will be discarded and be considered as wrong value.
forex_interval = 600  # in second
forex_valid_range = 0.2
forex_reference = {
    "USD-KRW": 1080,
    "USD-SGD": 1.35,
    "USD-EUR": 0.82,
}


class UnknownWsExchange(Exception):
  pass


class Forex(object):
  # Example query
  # https://api.fixer.io/latest?base=USD

  def __init__(self):
    self._per_base_ccy = {}
    for ccy_pair in forex_reference.keys():
      chunks = ccy_pair.split('-')
      assert len(chunks) == 2
      base_ccy = chunks[0]
      quote_ccy = chunks[1]
      if base_ccy not in self._per_base_ccy:
        self._per_base_ccy[base_ccy] = []
      self._per_base_ccy[base_ccy].append(quote_ccy)
    self._data = {}
    self._url = 'https://api.fixer.io/latest'

  def _read_per_base_ccy(self, base_ccy):
    url = self._url + '?base=%s' % base_ccy
    req = urllib.request.Request(url)
    with urllib.request.urlopen(req) as f:
      json_str = f.read()
    return json.loads(json_str)

  def _update_data(self, key, value):
    assert key in forex_reference
    reference_price = forex_reference[key]
    lower_limit = reference_price * (1 - forex_valid_range)
    upper_limit = reference_price * (1 + forex_valid_range)
    if lower_limit < value and value < upper_limit:
      self._data[key] = value
    else:
      print("%s,%s was discarded" % (key, value))

  def query(self):
    for base_ccy, quote_list in self._per_base_ccy.items():
      try:
        obj = self._read_per_base_ccy(base_ccy)
        rates = obj["rates"]
        for quote_ccy in quote_list:
          if quote_ccy in rates:
            key = "%s-%s" % (base_ccy, quote_ccy)
            value = float(rates[quote_ccy])
            self._update_data(key, value)
      except Exception:
        pass

  def has(self, ccy_pair):
    return ccy_pair in self._data

  def get(self, ccy_pair):
    return self._data[ccy_pair]

  def get_all(self):
    return self._data


class Context(object):
  # If target_quote_ccy is empty, it means it follows the ccy in product, and
  # it does not convert quote ccy.
  def __init__(self, group, exchange, product, target_quote_ccy):
    self.price_det_amount_usd = 1000
    self.group = group
    self.exchange = exchange
    self.product = product
    self.last_updated = None
    if product.find('-') >= 0:
      self.quote_ccy = product.split('.')[0].split('-')[1]
    else:
      # TODO(inkyu): How about the futures?
      self.quote_ccy = 'USD'
    if target_quote_ccy:
      self.target_quote_ccy = target_quote_ccy
      self.ccy_factor = "%s-%s" % (target_quote_ccy, self.quote_ccy)
      self._price_type_suffix = "1m.%s" % self.ccy_factor
      assert self.ccy_factor in forex_reference
    else:
      self.target_quote_ccy = self.quote_ccy
      self.ccy_factor = ""
      self._price_type_suffix = "1m"

    if flags.FLAGS.basis_server_url:
      self.proxy = xmlrpc.client.ServerProxy(flags.FLAGS.basis_server_url)
    else:
      print("It runs as simulation mode. It will just print price.")
      self.proxy = None

  def ask_price_type(self):
    return "ask_price_%s" % self._price_type_suffix

  def bid_price_type(self):
    return "bid_price_%s" % self._price_type_suffix

  def mid_price_type(self):
    return "mid_price_%s" % self._price_type_suffix

  def __str__(self):
    return "%s.%s.%s.%s" % (self.group, self.exchange, self.product, self._price_type_suffix)


class PriceCalculator(object):
  @staticmethod
  def get_price_det_amount(context, forex):
    if context.quote_ccy != "USD":
      rate = forex.get("USD-%s" % context.quote_ccy)
    else:
      rate = 1
    return context.price_det_amount_usd * rate

  @staticmethod
  def adjust_price(raw_price, context, forex):
    if context.ccy_factor:
      return raw_price / forex.get(context.ccy_factor)
    else:
      return raw_price

  @classmethod
  def calc_kr_rest_price(cls, context, kr_rest_feed, forex):
    kr_rest_feed.query_book()
    price_det_amount = cls.get_price_det_amount(context, forex)
    agg_price = kr_rest_feed.get_price_by_notional(price_det_amount)
    if agg_price is None:
      return None, None
    ask_price = cls.adjust_price(agg_price.ask_price, context, forex)
    bid_price = cls.adjust_price(agg_price.bid_price, context, forex)
    return ask_price, bid_price

  @classmethod
  def calc_ws_price(cls, context, book, forex):
    try:
      price_det_amount = cls.get_price_det_amount(context, forex)
      raw_ask_price = book.get_ask_price_by_amount(price_det_amount)
      raw_bid_price = book.get_bid_price_by_amount(price_det_amount)
    except Exception:
      return None, None
    ask_price = cls.adjust_price(raw_ask_price, context, forex)
    bid_price = cls.adjust_price(raw_bid_price, context, forex)
    return ask_price, bid_price


class BitfinexBookBuilder(object):
  def __init__(self, flow_sub):
    self._feed_handler = BitfinexV2Handler()
    flow_sub.subscribe('bitfinex_v2_ws_book_*', self._feed_handler.get_handler('ws'))

  def subscribe(self, products, callback):
    native_symbols = [BitfinexProduct.FromProduct(product).native_symbol for product in products]
    self._feed_handler.subscribe(native_symbols, callback)


class BinanceBookBuilder(object):
  def __init__(self, flow_sub):
    self._initialized = False
    self._feed_handler = BinanceHandler()
    flow_sub.subscribe('binance_ws_depth_*', self._feed_handler.get_handler('ws'))
    flow_sub.subscribe('binance_rest_depth_*', self._feed_handler.get_handler('rest'))

  def subscribe(self, products, callback):
    native_symbols = [BinanceProduct.FromProduct(product).native_symbol for product in products]
    self._feed_handler.subscribe(native_symbols, callback)


class BitmexBookBuilder(object):
  def __init__(self, flow_sub):
    self._feed_handler = BitmexHandler()
    flow_sub.subscribe('bitmex_ws_orderBookL2_*', self._feed_handler.get_handler('ws'))

  def subscribe(self, products, callback):
    native_symbols = [
        BitmexFuturesProduct.FromProduct(product).native_symbol for product in products
    ]
    self._feed_handler.subscribe(native_symbols, callback)


class WsClient(object):
  def __init__(self):
    self._binance_initialized = False
    self.queue = SimpleQueue('ws_queue')
    self.flow_sub = flow_subscriber.from_queue(self.queue)
    self._binance_last_book_ts = None
    self._binance_subscriber = None
    self._bitmex_last_book_ts = None
    self._bitmex_subscriber = None
    self._ioloop = tornado.ioloop.IOLoop.instance()

  def init_bitfinex(self, products):
    WORKER_ID = 1
    native_symbols = [BitfinexProduct.FromProduct(product).native_symbol for product in products]
    self.bitfinex_book = BitfinexBookBuilder(self.flow_sub)
    self.bitfinex_sub = \
        BitfinexV2Subscriber(self.queue, 'a', WORKER_ID, native_symbols)
    self.bitfinex_sub.start(tornado.ioloop.IOLoop.instance())

  def subscribe_bitfinex(self, product, callback):
    self.bitfinex_book.subscribe([product], callback)

  def monitor_binance_feed(self):
    if self._binance_last_book_ts is None:
      return

    diff = get_timestamp() - self._binance_last_book_ts
    logging.info('Binance last feed: %d ms ago' % (diff / 10**6))
    if diff < 2 * 60 * (10**9):  # 2mins
      return

    try:
      if self.binance_sub is not None:
        self.binance_sub.stop()
    except Exception:
      logging.exception('Error stopping binance subscriber')
    self._ioloop.stop()
    os._exit(1)

  def monitor_bitmex_feed(self):
    if self._bitmex_last_book_ts is None:
      return

    diff = get_timestamp() - self._bitmex_last_book_ts
    logging.info('Bitmex last feed: %d ms ago' % (diff / 10**6))
    if diff < 1 * 30 * (10**9):  # 30sec
      return

    try:
      if self.bitmex_sub is not None:
        self.bitmex_sub.stop()
    except Exception:
      logging.exception('Error stopping bitmex subscriber')
    self._ioloop.stop()
    os._exit(1)

  def init_binance(self, products):
    WORKER_ID = 1
    native_symbols = [BinanceProduct.FromProduct(product).native_symbol for product in products]
    self.binance_book = BinanceBookBuilder(self.flow_sub)
    self.binance_sub = \
        BinanceSubscriber(self.queue, 'a', WORKER_ID, native_symbols)
    self.binance_sub.start(self._ioloop)

  def on_binance_ws_record(self, products, record, queue_data, topic_data):
    if not self._binance_initialized:
      WORKER_ID = 1
      native_symbols = [BinanceProduct.FromProduct(product).native_symbol for product in products]
      self.binance_book_queryer = BinanceBookQueryer(self.queue, WORKER_ID, native_symbols)
      self.binance_book_queryer.start(tornado.ioloop.IOLoop.instance())
      self._binance_initialized = True

  def _wrapper_binance(self, callback, *args):
    self._binance_last_book_ts = get_timestamp()
    return callback(*args)

  def subscribe_binance(self, products, callback):
    self.binance_book.subscribe(products, functools.partial(self._wrapper_binance, callback))
    self.flow_sub.subscribe('binance_ws_depth_*',
                            functools.partial(self.on_binance_ws_record, products))
    # Setup feed monitor
    self._binance_monitor_periodic_callabck = PeriodicCallback(self.monitor_binance_feed, 10 * 1000)
    self._binance_monitor_periodic_callabck.start()

  def init_bitmex(self, products):
    WORKER_ID = 1
    native_symbols = [
        BitmexFuturesProduct.FromProduct(product).native_symbol for product in products
    ]
    self.bitmex_book = BitmexBookBuilder(self.flow_sub)
    self.bitmex_sub = \
        BitmexSubscriber(self.queue, 'a', WORKER_ID)
    self._bitmex_last_book_ts = get_timestamp()
    self.bitmex_sub.start(tornado.ioloop.IOLoop.instance())

  def _wrapper_bitmex(self, callback, *args):
    self._bitmex_last_book_ts = get_timestamp()
    return callback(*args)

  def subscribe_bitmex(self, products, callback):
    self.bitmex_book.subscribe(products, functools.partial(self._wrapper_bitmex, callback))
    # Setup feed monitor
    self._bitmex_monitor_periodic_callabck = PeriodicCallback(self.monitor_bitmex_feed, 10 * 1000)
    self._bitmex_monitor_periodic_callabck.start()


class Handler(object):
  forex = Forex()
  ws_client = WsClient()
  executor = ThreadPoolExecutor(max_workers=25)

  @run_on_executor
  def _update_forex(self):
    self.forex.query()
    print(self.forex.get_all())

  @tornado.gen.coroutine
  def update_forex(self):
    try:
      yield self._update_forex()
    finally:
      tornado.ioloop.IOLoop.instance().call_later(forex_interval, self.update_forex)

  @run_on_executor
  def _update_kr_rest_price(self, context, kr_rest_feed):
    ask_price, bid_price = PriceCalculator.calc_kr_rest_price(context, kr_rest_feed, self.forex)
    if ask_price is not None and bid_price is not None:
      mid_price = (ask_price + bid_price) * 0.5
      print(context.exchange, context.product, ask_price, bid_price)
      if context.proxy:
        context.proxy.update_price(context.group,
                                   context.exchange,
                                   context.product,
                                   context.ask_price_type(),
                                   ask_price)
        context.proxy.update_price(context.group,
                                   context.exchange,
                                   context.product,
                                   context.bid_price_type(),
                                   bid_price)
        context.proxy.update_price(context.group,
                                   context.exchange,
                                   context.product,
                                   context.mid_price_type(),
                                   mid_price)

  @run_on_executor
  def _update_ws_price(self, context, ask_price, bid_price):
    try:
      print(context.exchange, context.product, ask_price, bid_price)
      if context.proxy:
        mid_price = (ask_price + bid_price) * 0.5
        context.proxy.update_price(context.group,
                                   context.exchange,
                                   context.product,
                                   context.ask_price_type(),
                                   ask_price)
        context.proxy.update_price(context.group,
                                   context.exchange,
                                   context.product,
                                   context.bid_price_type(),
                                   bid_price)
        context.proxy.update_price(context.group,
                                   context.exchange,
                                   context.product,
                                   context.mid_price_type(),
                                   mid_price)
    except Exception:
      pass

  @tornado.gen.coroutine
  def update_kr_rest_price(self, context, kr_rest_feed):
    try:
      yield self._update_kr_rest_price(context, kr_rest_feed)
    finally:
      tornado.ioloop.IOLoop.instance().call_later(query_interval,
                                                  self.update_kr_rest_price,
                                                  context,
                                                  kr_rest_feed)

  @tornado.gen.coroutine
  def on_ws_book(self, context, book):
    # Since |book| is used in io thread, and heavily updated in io thead,
    # it is not safe to pass |book| to thread pool. Hence, all operation on
    # |book| is done here, and we pass the result only.
    ask_price, bid_price = \
        PriceCalculator.calc_ws_price(context, book, self.forex)
    if ask_price is not None and bid_price is not None:
      yield self._update_ws_price(context, ask_price, bid_price)

  @tornado.gen.coroutine
  def on_ws_book_multi(self, contexts, book):
    # TODO(inkyu): Clean up
    key = SharedProduct.FromProduct(book.product).native_symbol.replace('USDT', 'USD')
    ask_price, bid_price = \
        PriceCalculator.calc_ws_price(contexts[key], book, self.forex)
    if ask_price is not None and bid_price is not None:
      yield self._update_ws_price(contexts[key], ask_price, bid_price)

  @tornado.gen.coroutine
  def on_ws_book_bitmex(self, contexts, book):
    # TODO(inkyu): Implement me
    if not book.symbol == 'XBTUSD':
      return
    key = 'BTC-USD'
    context = contexts[key]
    if context.last_updated and get_timestamp() - context.last_updated < 0.5e9:
      return
    context.last_updated = get_timestamp()
    ask_price, bid_price = \
        PriceCalculator.calc_ws_price(context, book, self.forex)
    if ask_price is not None and bid_price is not None:
      yield self._update_ws_price(context, ask_price, bid_price)

  def register_ws_price(self, contexts):
    # TODO(neo): configure ws_client at a time
    # TODO(inkyu): Merge these
    bitfinex_contexts = []
    binance_contexts = {}
    bitmex_contexts = {}

    for context in contexts:
      if context.exchange.upper() == "BITFINEX":
        bitfinex_contexts.append(context)
      elif context.exchange.upper() == "BINANCE":
        binance_contexts[context.product] = context
      elif context.exchange.upper() == "BITMEX":
        bitmex_contexts[context.product] = context
      else:
        raise UnknownWsExchange()
    # Register bitfinex products
    self.ws_client.init_bitfinex(
        [SharedProduct.FromStr(context.product) for context in bitfinex_contexts])
    for context in bitfinex_contexts:
      self.ws_client.subscribe_bitfinex(SharedProduct.FromStr(context.product),
                                        functools.partial(self.on_ws_book, context))
    # Register binance products
    binance_products = [
        SharedProduct.FromStr(context.product) for context in binance_contexts.values()
    ]
    self.ws_client.init_binance(binance_products)
    self.ws_client.subscribe_binance(binance_products,
                                     functools.partial(self.on_ws_book_multi, binance_contexts))
    # Register bitmex products
    bitmex_products = []
    for context in bitmex_contexts.values():
      # TODO(inkyu): Implement me
      assert context.product == 'BTC-USD'
      bitmex_products.append(BitmexFuturesProduct.FromStr('XBTUSD'))
    self.ws_client.init_bitmex(bitmex_products)
    self.ws_client.subscribe_bitmex(bitmex_products,
                                    functools.partial(self.on_ws_book_bitmex, bitmex_contexts))

  def register_kr_rest_price(self, contexts):
    for context in contexts:
      kr_rest_feed = generate_feed_subscriber("Spot", context.exchange, None, context.product)
      tornado.ioloop.IOLoop.instance().add_callback(self.update_kr_rest_price,
                                                    context,
                                                    kr_rest_feed)

  def register_kr_rest_ws_price(self, contexts):
    for context in contexts:
      kr_rest_ws_feed = generate_ws_feed_subscriber("Spot", context.exchange, None, context.product)
      tornado.ioloop.IOLoop.instance().add_callback(self.update_kr_rest_price,
                                                    context,
                                                    kr_rest_ws_feed)

  def register_price_callbacks(self, contexts):
    contexts_ws = []
    contexts_kr_rest = []
    contexts_kr_rest_ws = []
    for context in contexts:
      feed_type = exchange_config[context.exchange]["feed_type"]
      if feed_type == "ws":
        contexts_ws.append(context)
      elif feed_type == "kr_rest":
        contexts_kr_rest.append(context)
      elif feed_type == "kr_rest_ws":
        contexts_kr_rest_ws.append(context)
      else:
        raise UnknownExchange()
    self.register_ws_price(contexts_ws)
    self.register_kr_rest_price(contexts_kr_rest)
    self.register_kr_rest_ws_price(contexts_kr_rest_ws)

  def register_forex_callback(self):
    tornado.ioloop.IOLoop.instance().add_callback(self.update_forex)


def register_basis_pairs(group, contexts):
  if not flags.FLAGS.basis_server_url:
    return
  proxy = xmlrpc.client.ServerProxy(flags.FLAGS.basis_server_url)
  for context in contexts:
    if context.group == group:
      price_types = [context.ask_price_type(), context.bid_price_type(), context.mid_price_type()]
      for price_type in price_types:
        proxy.add_product(context.group, context.exchange, context.product, price_type)
      proxy.add_pair("spread.%s" % context.product,
                     context.group,
                     context.exchange,
                     context.product,
                     context.ask_price_type(),
                     context.group,
                     context.exchange,
                     context.product,
                     context.bid_price_type())

  for group, pair_list in basis_pairs.items():
    for exchange_a, exchange_b in pair_list:
      contexts_a = [
          context for context in contexts
          if context.group == group and context.exchange == exchange_a
      ]
      contexts_b = [
          context for context in contexts
          if context.group == group and context.exchange == exchange_b
      ]
      for context_a in contexts_a:
        for context_b in contexts_b:
          for price_type in ["mid", "ask", "bid"]:
            func_name = "%s_price_type" % price_type
            context_a_price_type = eval("context_a.%s()" % func_name)
            context_b_price_type = eval("context_b.%s()" % func_name)
            if context_a.product.split('-')[0] == \
               context_b.product.split('-')[0] and  \
               context_a_price_type.split('.')[0] == \
               context_b_price_type.split('.')[0]:
              proxy.add_pair(
                  "basis.%s.%s-%s" %
                  (price_type, context_a.product.split('-')[0], context_a.target_quote_ccy),
                  context_a.group,
                  context_a.exchange,
                  context_a.product,
                  context_a_price_type,
                  context_b.group,
                  context_b.exchange,
                  context_b.product,
                  context_b_price_type)


def parse_coin_group():
  result = re.match("([a-z_]*)\[([0-9]*)\]$", flags.FLAGS.coin_group)
  if flags.FLAGS.coin_group == '' or result is None:
    print('specify --coin_group flags')
    sys.exit(1)

  coin_group_name = result.group(1)
  coin_group_index = int(result.group(2))
  return coin_group_name, coin_group_index


def get_coin_contexts(coin_group_name, coin_group_index):
  coin_group = coin_groups[coin_group_name][coin_group_index]
  contexts = []
  for exchange, coins in coin_group.items():
    for coin, quote_ccy in coins:
      context = Context(coin_group_name, exchange, coin, quote_ccy)
      contexts.append(context)
  return contexts


def _terminate():
  io_loop = tornado.ioloop.IOLoop.instance()
  io_loop.stop()
  os._exit(1)


def main(argv):
  try:
    argv = flags.FLAGS(argv)  # parse flags
  except flags.FlagsError as e:
    print('%s\\nUsage: %s ARGS\\n%s' % (e, sys.argv[0], flags.FLAGS))
    sys.exit(1)

  coin_group_name, coin_group_index = parse_coin_group()
  contexts = get_coin_contexts(coin_group_name, coin_group_index)

  handler = Handler()
  handler.register_price_callbacks(contexts)
  handler.register_forex_callback()
  register_basis_pairs(coin_group_name, contexts)
  io_loop = tornado.ioloop.IOLoop.instance()
  io_loop.add_timeout(datetime.timedelta(minutes=5), _terminate)
  io_loop.start()


if __name__ == '__main__':
  logging.basicConfig(level='DEBUG', format='%(levelname)8s %(asctime)s %(name)s] %(message)s')
  main(sys.argv)
